﻿/*    
    This file is part of d-mixr.

    d-mixr is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    d-mixr is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with d-mixr.  If not, see <http://www.gnu.org/licenses/>.
*/

using CoreAudioApi;
using CoreAudioApi.Interfaces;
using System;
using System.Diagnostics;
using System.Xml.Serialization;

namespace Dmixr.Model
{
    public class ChannelStrip: IAudioSessionEvents
    {
        private string m_name;
        private AudioSessionControl m_sessionControl;

        private MidiBinding m_volumeBinding = new MidiBinding();
        private MidiBinding m_muteBinding = new MidiBinding();

        // A delegate type for hooking up change notifications.
        public delegate void NewVolumeHandler(float NewVolume, bool newMute);
        public event NewVolumeHandler NewVolumeEvent;

        // A delegate type for hooking up change notifications.
        public delegate void ConnectionStateHandler(bool isConnected);
        public event ConnectionStateHandler ConnectionStateEvent;

        public ChannelStrip()
        {
            Session = null;
        }

        public void Init()
        {
            m_muteBinding.DataChanged += this.MuteDataChanged;
            m_volumeBinding.DataChanged += this.VolumeDataChanged;

            MidiSystem.Instance.AddMidiBinding(m_volumeBinding);
            MidiSystem.Instance.AddMidiBinding(m_muteBinding);
        }

        public void Term()
        {
            m_muteBinding.DataChanged -= this.MuteDataChanged;
            m_volumeBinding.DataChanged -= this.MuteDataChanged;

            MidiSystem.Instance.RemoveMidiBinding(m_volumeBinding);
            MidiSystem.Instance.RemoveMidiBinding(m_muteBinding);
        }

        public string Name
        {
            get
            {
                return m_name;
            }
            set
            {
                m_name = value;
                Session = null;
                if (ConnectToAudioSession())
                {
                    // ForceUI to refresh
                    if (NewVolumeEvent != null)
                    {
                        NewVolumeEvent(0.0f, false);
                    }

                    UpdateMidiDevice();
                }
            }
        }

        [XmlIgnoreAttribute]
        public float Volume
        {
            get
            {
                if (Session == null)
                {
                    if (!ConnectToAudioSession())
                        return 0;
                }
                
                return Session.SimpleAudioVolume.MasterVolume;
            }
            set
            {
                if (Session == null)
                {
                    if (!ConnectToAudioSession())
                        return;
                }
                
                Session.SimpleAudioVolume.MasterVolume = value;
            }
        }

        [XmlIgnoreAttribute]
        public bool IsMuted
        {
            get
            {
                if (Session == null)
                {
                    if (!ConnectToAudioSession())
                        return false;
                }
                
                return Session.SimpleAudioVolume.Mute;
            }

            set
            {
                if (Session == null)
                {
                    if (!ConnectToAudioSession())
                        return;
                }
                    
                Session.SimpleAudioVolume.Mute = value;
            }
        }

        [XmlIgnoreAttribute]
        public AudioSessionControl Session
        {
            get
            {
                return m_sessionControl;
            }
            set
            {
                // Unregister notif from previous AudioSession
                if (m_sessionControl != null)
                {
                    m_sessionControl.UnregisterAudioSessionNotification(this);
                }

                m_sessionControl = value;

                // Register to notifications
                if (m_sessionControl != null)
                {
                    m_sessionControl.RegisterAudioSessionNotification(this);
                }

                if (ConnectionStateEvent != null)
                {
                    ConnectionStateEvent(m_sessionControl != null);
                }
            }
        }

        // Try to connect to an audio session that was started by a certain process name
        public bool ConnectToAudioSession()
        {
            if (Name == null || Name.Length == 0)
                return false;

             int sessionCount = AudioSystem.Instance.DefaultDevice.AudioSessionManager.Sessions.Count;

            for (int i = 0; i < sessionCount; i++)
            {
                AudioSessionControl it = AudioSystem.Instance.DefaultDevice.AudioSessionManager.Sessions[i];

                if (it.State == AudioSessionState.AudioSessionStateExpired)
                {
                    continue;
                }

                uint processID = it.ProcessID;

                Process p = null;
                try
                {
                   p = Process.GetProcessById((int)processID);
                }
                catch
                {
                    continue;
                }

                if (p == null)
                {
                    continue;
                }

                if (p.ProcessName.ToLower() == Name.ToLower())
                {
                    Session = it;
                    return true;
                }
            }
            return false;
        }

        #region IAudioSessionEvents
        public int OnDisplayNameChanged(string NewDisplayName, Guid EventContext)
        {
            //throw new NotImplementedException();
            return 0;
        }

        public int OnIconPathChanged(string NewIconPath, Guid EventContext)
        {
            //throw new NotImplementedException();
            return 0;
        }

        public void UpdateMidiDevice()
        {
            if (MidiSystem.Instance.OutputDevice != null)
            {
                if (m_volumeBinding.Channel >= 0 && m_volumeBinding.ControlChange >= 0)
                {
                    int val = (int)((AudioHelpers.LinearToDb(Volume) + 96.0) / 96.0 * 127.0);
                    MidiSystem.Instance.OutputDevice.SendControlChange((Midi.Channel)m_volumeBinding.Channel, (Midi.Control)m_volumeBinding.ControlChange, val);
                }

                if (m_muteBinding.Channel >= 0 && m_muteBinding.ControlChange >= 0)
                {
                    MidiSystem.Instance.OutputDevice.SendControlChange((Midi.Channel)m_muteBinding.Channel, (Midi.Control)m_muteBinding.ControlChange, IsMuted ? 127 : 0);
                }

            }
        }

        public int OnSimpleVolumeChanged(float NewVolume, bool newMute, Guid EventContext)
        {
            if (NewVolumeEvent != null)
            {
                NewVolumeEvent(NewVolume, newMute);
            }

            UpdateMidiDevice();

            return 0;
        }

        public int OnChannelVolumeChanged(uint ChannelCount, IntPtr NewChannelVolumeArray, uint ChangedChannel, Guid EventContext)
        {
            return 0;
        }

        public int OnGroupingParamChanged(Guid NewGroupingParam, Guid EventContext)
        {
            return 0;
        }

        public int OnStateChanged(AudioSessionState NewState)
        {
            if (NewState == AudioSessionState.AudioSessionStateExpired ||
                NewState == AudioSessionState.AudioSessionStateInactive)
            {
                Session = null;
            }

            return 0;
        }

        public MidiBinding VolumeMidiBinding
        {
            get
            {
                return m_volumeBinding;
            } 
        }

        public MidiBinding MuteMidiBinding
        {
            get
            {
                return m_muteBinding;
            }
        }

        void MuteDataChanged(int in_value)
        {
            IsMuted = in_value > 0;
        }

        void VolumeDataChanged(int in_value)
        {
            Volume = (float)AudioHelpers.DbToLinear(  ((double)(in_value) / 127.0 - 1.0)* 96);
        }

        public int OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
        {
            //throw new NotImplementedException();
            return 0;
        }
        #endregion IAudioSessionEvents
    }
}
